/****************************************************************************
**
**	File Name   : ObjViewDlg.h
**
**	Project     : DatChecker
**
**	Last Updated: $Header: /DatChecker.root/DatChecker/ObjViewDlg.h 4     1/15/04 12:45p Neusel $
**
**	Description : 
**
**		CObjViewDlg class interface and implementation.
**
****************************************************************************/
#ifndef	H_OBJVIEWDLG
	#define H_OBJVIEWDLG	__TIMESTAMP__

#if defined(_MSC_VER) && (_MSC_VER >= 1020)
#	pragma once
#endif

#include <math.h>

extern Options			theOptions;

class CObjViewDlg :	public CDialogImpl<CObjViewDlg>,
					public CDialogResize<CObjViewDlg>,
					public COffscreenDraw<CObjViewDlg>
	{
public:
	enum { IDD = IDD_OBJVIEW };

protected:
	int					m_nClip,
						m_nImage;
	CDib				m_dibObj;
	ObjInfo	const &		m_objInfo;
	XBuffer				m_imageData;
	
	bool
	buildImage(int nImage)
		{
		if (nImage < 0 || nImage >= m_objInfo.nImages || 
			m_imageData.GetCount() < sizeof(SV6ObjImageDir))
			{
			return false;
			}
		//
		//	Parse through the object data to get to the start of the graphics data.
		//	The actual data starts just past the image directory; since the defined
		//	structure only includes one (1) image, adjust the offset to take into 
		//	account addition images.
		//
		SV6ObjImageDirPtr	pIDir;
		SIZE_T				sOffset;
		CString				strErrMsg;
		bool				bValid = false;

		try	
			{
			pIDir = reinterpret_cast<SV6ObjImageDirPtr>( m_imageData.GetData() );
			if (NULL_PTR(pIDir))
				{
				strErrMsg = _T("Could not locate internal image directory.");
				throw DWORD( ERROR_INVALID_DATA );
				}
				
			sOffset = sizeof(SV6ObjImageDir) + 
						((pIDir->dwImageCount - 1) * sizeof(SV6ObjImage));

			if ((sOffset + pIDir->dwScanLines) > m_imageData.GetCount())
				{
				strErrMsg = _T("Could not locate internal image data.");
				throw DWORD( ERROR_INVALID_DATA );
				}

			if (nImage < 0 || pIDir->dwImageCount < DWORD(nImage))
				{
				strErrMsg = _T("Could not find requested image.");
				throw DWORD( ERROR_INVALID_PARAMETER );
				}
		
			if (!m_dibObj.IsEmpty())
				m_dibObj.DeleteObject();	// delete the existing DIB
			//
			//	Now start building a bitmap object.  Create the DIB and fill
			//	it with the "transparent" color. 
			//
			SV6ObjImage *	pImage = &pIDir->image[nImage];

			if (m_dibObj.Create(pImage->nWidth, pImage->nHeight, 24))
				m_dibObj.SetAllPixels( theOptions.clrTransparent );
			else
				{
				ATLTRACE("Could not create image DIB: error 0x%08lX\n", ::GetLastError());
				return false;
				}

			sOffset += pImage->dwStart;	// => first byte of scanline data
			
			switch (pImage->wFlags & 0x0F)
				{
				case 0x01:	// direct bitmap
					if (!parseBitmap(pImage->nWidth, pImage->nHeight, m_imageData.GetData() + sOffset))
						{
						strErrMsg = _T("Could not create image DIB.");
						throw DWORD( ::GetLastError() );
						}
					break;
				
				case 0x05:	// compacted bitmap
					if (!parseCompactBitmap(pImage->nWidth, pImage->nHeight, m_imageData.GetData() + sOffset))
						{
						strErrMsg = _T("Could not create image DIB.");
						throw DWORD( ::GetLastError() );
						}
					break;
					
				default:
					strErrMsg = _T("Unrecognized image data format.");
					throw DWORD( ERROR_INVALID_DATA );
				}
				
			bValid = true;
			}
		catch (DWORD dwError)
			{
			if (strErrMsg.IsEmpty())
				strErrMsg.Format(_T("Could not display image: error 0x%08lX."), dwError);

			(void) ::MessageBox(NULL, LPCTSTR(strErrMsg), kStrAppTitle, MB_OK|MB_ICONSTOP);
			}
			
		return bValid;
		}

	bool				
	displayImage(int nImage)
		{
		ATLTRACE(	"displayImage() => image #%d\n", nImage);

		if (nImage != m_nImage && buildImage(nImage))
			{
			CString		strTmp;
			
			strTmp.Format(_T("Image #%d of %d"), (nImage + 1), m_objInfo.nImages);		
			SetWindowText(strTmp);
			//
			//	Update the pane info
			//
			CStatic	ctlInfo = GetDlgItem(IDC_IMAGE_INFO);
			
			if (ctlInfo.IsWindow())
				{
				if (m_dibObj.GetWidth() == 1 && m_dibObj.GetHeight() == 1)
					ctlInfo.SetWindowText(_T("1 x 1 pixel"));
				else
					{
					strTmp.Format(	_T("%d x %d pixels"), 
									m_dibObj.GetWidth(), m_dibObj.GetHeight());
					ctlInfo.SetWindowText(strTmp);
					}
				}
				
			m_nImage = nImage;
			InvalidateRect(NULL);
			}

		return true;
		}			

	bool
	parseBitmap(const int kWidth, const int kHeight, BYTE const * pBits)
		{
		BYTE	xVal;

		for (int nY = kHeight - 1; nY >= 0; --nY)
			for (int nX = 0; nX < kWidth; ++nX)
				{
				if ((xVal = *pBits++) == 0x00)
					continue;	// ignore "transparent" pixles
				
				m_dibObj.SetPixel(nX, nY, kObjPalette[xVal]);
				}

		return true;
		}

	bool
	parseCompactBitmap(const int kWidth, const int kHeight, BYTE const * pData)
		{
		int				nX, nY;
		WORD const *	pwRowStarts = MAKE_PTR(WORD const *, pData, 0);

		for (nY = kHeight - 1; nY >= 0; --nY)
			{
			bool			bLast = false;
			BYTE const *	pBits = &pData[*pwRowStarts++];
			//
			//	Process one scan
			//
			do
				{
				BYTE	xCount = *pBits++;
				
				if (xCount & 0x80)		// MSB is set if last scan element in this row
					{
					xCount &= ~0x80;	// mask out the MSB flag to get actual count
					bLast = true;
					}

				nX = int(*pBits++);	// get the offset
				for (int ndx = 0; ndx < int(xCount); ++ndx)	//	count can be ZERO!
					{
					ATLASSERT(nX >= 0 && nX < kWidth);
					ATLASSERT(nX >= 0 && nY < kHeight);
					m_dibObj.SetPixel(nX++, nY, kObjPalette[*pBits++]);
					}
				}
			while (!bLast);
			}

		return true;
		}

public:
	void
	DoPaint(CDCHandle	dc)
		{
		CRect			rTmp;
		
		GetClientRect(rTmp);
		dc.FillSolidRect(rTmp, ::GetSysColor(COLOR_3DFACE) );
		//
		//	Don't bother with the DIB painting if the DIB doesn't exist yet.
		//	I changed the window sizing in response to WM_INITDIALOG messages
		//	and this function started firing assertions in m_dibObj.GetWidth().
		//
		CStatic	ctrlView = GetDlgItem(IDC_VIEW_AREA);

		if (!ctrlView.IsWindow() || m_dibObj.IsEmpty())
			return;
		//
		//	Constrain image to 16 time normal size and force a 1:1 aspect
		//
		const CSize	dimObj(m_dibObj.GetWidth(), m_dibObj.GetHeight());

		ctrlView.GetWindowRect(rTmp);
		ScreenToClient(rTmp);
		
		if (dimObj.cx > 0 && dimObj.cy > 0)
			{
			double	dScale = min(	double(rTmp.Width()) / double(dimObj.cx), 
									double(rTmp.Height()) / double(dimObj.cy));

			if (dScale > 16.0)
				dScale = 16.0;
			else if (dScale > 1.0)
				dScale = floor(dScale);	
				
			rTmp.right = rTmp.left + int(dimObj.cx * dScale);
			rTmp.bottom = rTmp.top + int(dimObj.cy * dScale);

			m_dibObj.Stretch(dc, rTmp);
			//
			//	Now add the gridlines if they're turned on and if the scale
			//	is x8 or bigger
			//
			extern Options	theOptions;
	
			if (theOptions.bGridLines &&
				dScale >= 5.0 &&
				(dimObj.cx > 1 || dimObj.cy > 1))
				{
				CPen	pen;
				double	dGrid;
				HPEN	hpenOld;
				
				ATLVERIFY( pen.CreatePen(PS_SOLID, 1, ::GetSysColor(COLOR_3DFACE)) );
				hpenOld = dc.SelectPen(pen);

				dGrid = double(rTmp.Height()) / double(dimObj.cy);
				for (int nRow = 0; nRow < dimObj.cy; ++nRow)
					{
					dc.MoveTo(rTmp.left, rTmp.top + int(nRow * dGrid));
					dc.LineTo(rTmp.right, rTmp.top + int(nRow * dGrid));
					}

				dGrid = double(rTmp.Width()) / double(dimObj.cx);
				for (int nCol = 0; nCol < dimObj.cx; ++nCol)
					{
					dc.MoveTo(rTmp.left + int(nCol * dGrid), rTmp.top);
					dc.LineTo(rTmp.left + int(nCol * dGrid), rTmp.bottom);
					}
				
				(void) dc.SelectPen(hpenOld);
				}
			}
		}
//
//	CTOR / DTOR
//
	CObjViewDlg(ObjInfo const & objInfo) : m_nClip(-1), m_nImage(-1), m_objInfo(objInfo)
		{ /* STUB CTOR */ }
							
	virtual				
	~CObjViewDlg()
		{ 
		if (!m_dibObj.IsEmpty()) 
			m_dibObj.DeleteObject(); 

		m_imageData.RemoveAll();
		}

/*
**	ATL/WTL VooDoo Code	
*/
protected:
	LRESULT				
	onClip(WORD, WORD wID, HWND, BOOL &)
		{
		if (m_nClip != m_nImage && OpenClipboard()) 
			{
			if (m_dibObj.CopyToClipboard())
				{
				m_nClip = m_nImage;	// remember last image copied
				ATLTRACE(_T("Copied image #%d to clipboard.\n"), m_nClip);
				}
								
			CloseClipboard();			
			}

		return 0;
		}
	
	LRESULT				
	onCommand(WORD, WORD wID, HWND, BOOL &)
		{
		(void) GetWindowRect(&theOptions.rObjViewDlg);
		EndDialog(wID);
		return 0;
		}
		
	void
	onHScroll(UINT uCode, short nPos, HWND hwnd)
		{
		int		nImage;
		
		switch (uCode)
			{
			case SB_LEFT:			nImage = 0;						break;
			case SB_RIGHT:			nImage = m_objInfo.nImages;		break;
			case SB_LINELEFT:		nImage = m_nImage - 1;			break;
			case SB_LINERIGHT:		nImage = m_nImage + 1;			break;
			case SB_PAGELEFT:		nImage = m_nImage - 5;			break;
			case SB_PAGERIGHT:		nImage = m_nImage + 5;			break;
			case SB_THUMBPOSITION:	nImage = nPos;					break;
			default:
				ATLTRACE(_T("onHScroll(%u, %d, 0x%08lX)\n"), uCode, nPos, hwnd);
				return;
			}
		
		nImage = min(nImage, m_objInfo.nImages - 1);
		nImage = max(nImage, 0);
		
		if (nImage != m_nImage)
			{
			CScrollBar	ctlSBar = GetDlgItem(IDC_IMAGE_INDEX);
			
			ATLASSERT(hwnd == ctlSBar.m_hWnd);
			
			if (displayImage(nImage) && ctlSBar.IsWindow())
				ctlSBar.SetScrollPos(m_nImage);
			}
		}

	LRESULT				
	onInitDialog(HWND, LPARAM)
		{
		DlgResize_Init(false);
		
		if (LoadObjectImageData(m_objInfo, m_imageData) <= 0)
			(void) EndDialog(IDCANCEL);
		
		PlaceWindow(*this, theOptions.rObjViewDlg);
		(void) displayImage(0);
		//
		//	Update the scroll bar
		//
		CScrollBar		ctlSBar;
		
		ctlSBar = GetDlgItem(IDC_IMAGE_INDEX);
		ctlSBar.SetScrollPos(0, FALSE);
		ctlSBar.SetScrollRange(0, m_objInfo.nImages, FALSE);
		
		return TRUE;
		}
		
public:
	BEGIN_DLGRESIZE_MAP(CObjInfoDlg)
		DLGRESIZE_CONTROL(IDC_CLIP_IMAGE, DLSZ_MOVE_Y)
		DLGRESIZE_CONTROL(IDC_IMAGE_INDEX, DLSZ_SIZE_X)
		DLGRESIZE_CONTROL(IDC_IMAGE_INFO, DLSZ_MOVE_Y|DLSZ_SIZE_X)
		DLGRESIZE_CONTROL(IDC_VIEW_AREA, DLSZ_SIZE_X|DLSZ_SIZE_Y)
	END_DLGRESIZE_MAP()

	BEGIN_MSG_MAP_EX(CObjViewDlg)
		MSG_WM_INITDIALOG(onInitDialog)
		MSG_WM_HSCROLL(onHScroll)	

		COMMAND_ID_HANDLER(IDOK, onCommand)
		COMMAND_ID_HANDLER(IDCANCEL, onCommand)
		COMMAND_ID_HANDLER(IDC_CLIP_IMAGE, onClip)
		
		CHAIN_MSG_MAP(COffscreenDraw<CObjViewDlg>)
		CHAIN_MSG_MAP(CDialogResize<CObjViewDlg>)
	END_MSG_MAP()
	};
#endif	// H_OBJVIEWDLG
/****************************************************************************
**
**	$History: ObjViewDlg.h $
 * 
 * *****************  Version 4  *****************
 * User: Neusel       Date: 1/15/04    Time: 12:45p
 * Updated in $/DatChecker.root/DatChecker
 * v1.6 -- improved command line parsing, better graphics scaling, add
 * "Copy to Clipboard" button.
 * 
 * *****************  Version 3  *****************
 * User: Neusel       Date: 1/02/04    Time: 12:37p
 * Updated in $/DatChecker.root/DatChecker
 * Modified decompress() to use dynamically allocated arrays.
 * CObjViewDlg now caches image data.
 * 
 * *****************  Version 2  *****************
 * User: Neusel       Date: 12/31/03   Time: 4:48p
 * Updated in $/DatChecker.root/DatChecker
 * v1.5 -- Added object image viewing.
 * 
 * *****************  Version 1  *****************
 * User: Neusel       Date: 9/08/03    Time: 9:34a
 * Created in $/DatChecker.root/DatChecker
 * v1.2 -- added ObjData header file and internal checks.
**
**	-------------------------------------------------------------------------
**
**	End of ObjViewDlg.h
**
****************************************************************************/
